/* gawkmisc.c --- miscellaneous gawk routines that are OS specific. -*-C-*- */

/* 
 * Copyright (C) 1986, 1988, 1989, 1991 - 2003, 2012, 2016, 2017
 * the Free Software Foundation, Inc.
 * 
 * This file is part of GAWK, the GNU implementation of the
 * AWK Progamming Language.
 * 
 * GAWK is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GAWK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

char quote = '\'';
char envsep = ';';

# ifdef DEFPATH
char *defpath = DEFPATH;
# else
char *defpath = ".;c:\\lib\\awk;c:\\gnu\\lib\\awk";
# endif
/* the Makefile should define DEFLIBPATH */
char *deflibpath = DEFLIBPATH;

#ifdef __EMX__
#include<io.h>

static int _os2_is_abs_path(const char *dirname);
static char* _os2_unixroot(const char *path);
static const char* _os2_unixroot_path(const char *path);
#endif

#ifdef __MINGW32__
#ifdef HAVE_SOCKETS
#include <socket.h>

#undef socket
#undef setsockopt
#undef bind
#undef connect
#undef listen
#undef accept
#undef recvfrom
#undef shutdown
#endif

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

/* gawk_name --- pull out the "gawk" part from how the OS called us */

char *
gawk_name(filespec)
const char *filespec;
{
	char *p, *q;

	p = (char *) filespec;  /* Sloppy... */

	/* OS/2 allows / for directory separator too */
	if ((q = strrchr(p, '\\')) != NULL)
		p = q + 1;
	if ((q = strrchr(p, '/')) != NULL
	    && (p == NULL || q > p)) /* support mixed d:\foo/bar\gawk.exe */
		p = q + 1;
	if ((q = strchr(p, '.')) != NULL)
		*q = '\0';
	return strlwr(p);
}


/*
 * memcpy_long() & memset_ulong() are 32-bit replacements for MSC which
 * has a 16-bit size_t.
 */
char *
memcpy_ulong (dest, src, l)
register char *dest;
register const char *src;
register unsigned long l;
{
	register char *ret = dest;

	while (l--)
		*dest++ = *src++;

	return ret;
}


void *
memset_ulong(dest, val, l)
void *dest;
register int val;
register unsigned long l;
{
	register char *ret = dest;
	register char *d = dest;

	while (l--)
		*d++ = val;

	return ((void *) ret);
}


/* os_arg_fixup --- fixup the command line */

void
os_arg_fixup(argcp, argvp)
int *argcp;
char ***argvp;
{
#ifdef __EMX__
# ifdef initialize_main
	initialize_main(argcp, argvp);
# else
	_wildcard(argcp, argvp);
	_response(argcp, argvp);
# endif

	setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
	defpath = (char*) _os2_unixroot_path(defpath);
#endif /* __EMX__ */
	return;
}

/* os_devopen --- open special per-OS devices */

int
os_devopen(name, flag)
const char *name;
int flag;
{
#ifdef __EMX__
	/* do not use open(name, flag) here !!! */
	return -1;
#else
	if (strcmp(name, "/dev/null") == 0)
		return open("NUL", flag);
	/* FIXME: */
	/* else if (strcmp(name, "/dev/tty") == 0)
	 * 	return open("???", flag);
	 */
	return -1;
#endif
}

/* optimal_bufsize --- determine optimal buffer size */

size_t
optimal_bufsize(fd, stb)
int fd;
struct stat *stb;
{
	/* force all members to zero in case OS doesn't use all of them. */
	memset(stb, '\0', sizeof(struct stat));

	/*
	 * DOS doesn't have the file system block size in the
	 * stat structure. So we have to make some sort of reasonable
	 * guess. We use stdio's BUFSIZ, since that is what it was
	 * meant for in the first place.
	 */
#define	DEFBLKSIZE	BUFSIZ

	if (fstat(fd, stb) == -1)
		fatal("can't stat fd %d (%s)", fd, strerror(errno));
	if (S_ISREG(stb->st_mode)
	    && 0 < stb->st_size && stb->st_size < DEFBLKSIZE) /* small file */
		return stb->st_size;
	return DEFBLKSIZE;
}

/* ispath --- return true if path has directory components */

int
ispath(file)
const char *file;
{
#ifdef __EMX__
	return (strpbrk(file, "/\\") != NULL ||
		(toupper(file[0]) >= 'A' && toupper(file[0]) <= 'Z' && file[1] == ':'));
#else
	for (; *file; file++) {
		switch (*file) {
		case '/':
		case '\\':
		case ':':
			return 1;
		}
	}
	return 0;
#endif
}

/* isdirpunct --- return true if char is a directory separator */

int
isdirpunct(c)
int c;
{
	return (strchr(":\\/", c) != NULL);
}

/* os_close_on_exec --- set close on exec flag, print warning if fails */

void
os_close_on_exec(fd, name, what, dir)
int fd;
const char *name, *what, *dir;
{
#if (defined(__DJGPP__) && (__DJGPP__ > 2 || __DJGPP_MINOR__ >= 4)) || defined __EMX__
	if (fd <= 2)	/* sanity */
		return;

	if (fcntl(fd, F_SETFD, 1) < 0)
		warning("%s %s `%s': could not set close-on-exec: %s",
			what, dir, name, strerror(errno));
#endif
#ifdef __MINGW32__
	HANDLE fh = (HANDLE)_get_osfhandle(fd);

	if (fh && fh != INVALID_HANDLE_VALUE)
		SetHandleInformation(fh, HANDLE_FLAG_INHERIT, 0);
#endif
}

/* os_isdir --- is this an fd on a directory? */

#if ! defined(S_ISDIR) && defined(S_IFDIR)
#define	S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif

int
os_isdir(fd)
int fd;
{
	struct stat sbuf;

	return (fstat(fd, &sbuf) == 0 && S_ISDIR(sbuf.st_mode));
}

/* os_isreadable --- fd can be read from */

int
os_isreadable(const awk_input_buf_t *iobuf, bool *isdir)
{
	*isdir = false;

	switch (iobuf->sbuf.st_mode & S_IFMT) {
	case S_IFREG:
	case S_IFCHR:	/* ttys, /dev/null, .. */
#ifdef S_IFSOCK
	case S_IFSOCK:
#endif
#ifdef S_IFIFO
	case S_IFIFO:
#endif
		return true;
	case S_IFDIR:
		*isdir = true;
		/* fall through */
	default:
		return false;
	}
}

/* os_is_setuid --- true if running setuid root */

int
os_is_setuid()
{
#ifdef __EMX__
      	long uid, euid;

	uid = getuid();
	euid = geteuid();

	return (euid == 0 && euid != uid);
#else
	return 0;
#endif
}

/* os_setbinmode --- set binary mode on file */

#ifdef __DJGPP__
#include <sys/exceptn.h>
#include <io.h>
#endif
static int orig_tty_mode = -1;

int
os_setbinmode(fd, mode)
int fd, mode;
{
	int prev_mode = setmode(fd, mode);

#ifdef __DJGPP__
	if ((mode & O_BINARY) != 0)
		__djgpp_set_ctrl_c(1); /* allow to interrupt with Ctrl-C */
#endif
	/* Save the original tty mode as we found it.  */
	if (orig_tty_mode == -1 && fd >= 0 && fd <= 2)
		orig_tty_mode = prev_mode;
	return prev_mode;
}

/* os_restore_mode --- restore the original mode of the console device */

void
os_restore_mode (fd)
int fd;
{
	if (orig_tty_mode != -1) {
		setmode(fd, orig_tty_mode);
	}
}

/* os_isatty --- return true if fd is a tty */

int
os_isatty(int fd)
{
#if defined(__MINGW32__) || defined(_MSC_VER)
	return (isatty(fd) && lseek(fd, SEEK_CUR, 0) == -1);
#else
	return isatty(fd);
#endif
}
 
/* files_are_same --- return true if files are identical */

int
files_are_same(char *path, SRCFILE *src)
{
	struct stat st;
	size_t pathlen;
	char *p, *s;

	if (stat (path, & st) == 0) {
		/* If they have a working `stat', honor that.  */
		if (!(st.st_dev == src->sbuf.st_dev
		      && st.st_ino == src->sbuf.st_ino))
			return 0;

		/* Compare modification times.  */
		if (st.st_mtime != src->mtime)
			return 0;

		/* Compare absolute file names case-insensitively, and
		   treat forward- and back-slashes as equal.  */
		pathlen = strlen(path);
		for (p = path, s = src->fullpath;
		     p <= path + pathlen;
		     p++, s++) {
			if (tolower(*p) != tolower(*s)
			    && !((*p == '/' || *p == '\\')
				 && (*s == '/' || *s == '\\')))
				return 0;
		}
		return 1;
	}
	return 0;
}


#ifdef __EMX__ 
# ifndef PATH_SEPARATOR
#  define PATH_SEPARATOR ';'
# endif

/* result is 0 if dirname is no absolute path, 1 otherwise */

static int
_os2_is_abs_path(const char *dirname)
{
  int result = 0;
  if (dirname != NULL && dirname[0] != '\0') {
    /* if dirname contains a valid drive letter like "c:" */
    if (((dirname[0] >= 'A' && dirname[0] <= 'Z') || (dirname[0] >= 'a' && dirname[0] <= 'z'))
        && dirname[1] == ':') dirname += 2; /* remove the drive letter */

    if (dirname[0] == '/' || dirname[0] == '\\') result = 1; /* asbolute path */
  }

  return result;
}


/* path is assumed to be a list of directories separated by PATH_SEPARATOR.
   This function determines if the first directory of path is on the
   drive specified by the environment variable UNIXROOT.
   If it is the case, NULL is returned, otherwise a new directory name
   is allocated using the drive letter from UNIXROOT and returned as result.
   If the first directory is a relative path NULL is returned, too.
   The new directory name is allocated by malloc().
   Example (UNIXROOT is set to "e:"):
     "c:/usr/share" -> "e:/usr/share"
     "e:/usr/share" -> NULL             (already on the $UNIXROOT drive)
     "/usr/share"   -> "e:/usr/share"
     "."            -> NULL             (not an absolute path)
     "usr/share"    -> NULL             (not an absolute path)
     "c:usr/share"  -> NULL             (not an absolute path)
     "c:/usr/share;d:/etc" -> "e:/usr/share" (only the first directory in path is used) */

static char*
_os2_unixroot(const char *path)
{
  static const char *unixroot = NULL;
  static int unixroot_init = 0; 
  char *result = NULL; 

  if (unixroot_init == 0) {
    /* get $UNIXROOT only one time */
    unixroot = getenv("UNIXROOT");

    /* check whether unixroot is valid (must be "x:") */
    if (unixroot != NULL) {
      int drive = toupper(unixroot[0]);
      if (drive < 'A' || drive > 'Z' || unixroot[1] != ':' || unixroot[2] != '\0')
        unixroot = NULL; /* unixroot not valid */
    }

    unixroot_init = 1; /* initialized */
  }

  /* note: if unixroot != NULL then it contains a valid drive letter */
  if (unixroot != NULL && _os2_is_abs_path(path)) {
    /* dirname is an absolute path and unixroot is a drive letter, "c:" for example */
    size_t old_path_len = strlen(path);

    /* end points to the first ';' in path or to NULL */
    const char *end = strchr(path, PATH_SEPARATOR);

    /* dir_len is the length of the first directory in path */
    size_t dir_len = (end) ? end - path : old_path_len;

    if (toupper(unixroot[0]) != toupper(path[0]) || path[1] != ':') {
      /* the first directory of path does not start with the string $UNIXROOT */
      if (path[1] == ':') {
        /* if there is a drive letter remove it */
        dir_len -= 2;
        path += 2;
      } 

      result = malloc(dir_len + 3);
      if (result) { /* do nothing if we are out of memory */
        result[0] = unixroot[0];
        result[1] = unixroot[1];
        memcpy(result + 2, path, dir_len);
        result[dir_len + 2] = '\0';
      }
    }
  }
  return result;
}

/* path is assumed to be a list of directories separated by PATH_SEPARATOR.
   Every directory is processed. _os2_unixroot() is used to find out whether
   these directories are on the drive specified by the environment variable
   UNIXROOT. If this is not the case the same directory on the UNIXROOT drive
   is added to the end of path. If path is a valid path this function returns a valid path, too.
   Example ($UNIXROOT is set to "e:"):
   ".;c:/usr/local;d:/usr/local;d:/etc;e:/etc"
   -> ".;c:/usr/local;d:/usr/local;d:/etc;e:/etc;e:/usr/local;e:/usr/local;e:/etc" */

static const char*
_os2_unixroot_path(const char *path)
{
  char *result = NULL;
  const char *p = path;
  unsigned dir_count = 1;

  if (path == NULL || path[0] == '\0') return NULL; /* empty path */

  /* save number of path components in dir_count */
  while(*p) {
    if (*p++ == PATH_SEPARATOR && *p != '\0' && *p != PATH_SEPARATOR)
      dir_count += 1;
  }

  {
    const char *list[dir_count]; /* list of char pointers */
    size_t dir_len[dir_count]; /* the according directory length */
    size_t old_path_len = strlen(path); /* the old path length */
    size_t total_len;
    unsigned i = 0;

    if (path[old_path_len - 1] == PATH_SEPARATOR) /* last character is ';' */
      old_path_len--;

    list[0] = p = path; /* first directory */

    while(*p) {
    if (*p++ == PATH_SEPARATOR && *p != '\0' && *p != PATH_SEPARATOR)
      list[++i] = p;
    }
    /* now list[i] contains the ith directory of path (no 0-terminated strings!!!) */

    /* determine the total length for the new path */
    total_len = old_path_len;

    for(i = 0; i < dir_count; i++) {
      list[i] = _os2_unixroot(list[i]);
      if (list[i] != NULL) {
        dir_len[i] = strlen(list[i]);
        total_len += dir_len[i] + 1; /* one character for ';' or '\0' */
      }
      else dir_len[i] = 0;
    }
    /* now list[] contains the according directories on the UNIXROOT drive or NULL
       total_len contains the total length for the new path */
    result = malloc(total_len + 1);

    if (result) {
      /* copy the old path and the new directories into the new path */
      char *q = result;
      memcpy(q, path, old_path_len);
      q += old_path_len;

      for(i = 0; i < dir_count; i++) {
        if (dir_len[i] != 0) {
          *q++ = PATH_SEPARATOR;
          memcpy(q, list[i], dir_len[i]);
          q += dir_len[i];
        }
      }

      *q = '\0'; /* terminating '\0' */
    }

    for(i = 0; i < dir_count; i++) free((void*) list[i]);
  }

  return (result) ? (const char*) result : path;
}

/* limit a length of DLL name up to 8 characters. If dst is not enough for
   a fixed dll name, it is truncated. */
char *os2_fixdllname(char *dst, const char *src, size_t n)
{
    char drive[_MAX_DRIVE];
    char dir[_MAX_DIR];
    char name[_MAX_FNAME];
    char ext[_MAX_EXT];
    char dll_file[_MAX_PATH];

    _splitpath(src, drive, dir, name, ext);
    if (strlen(name) > 8)
        name[8] = '\0';
    _makepath(dll_file, drive, dir, name, ext);

    strncpy(dst, dll_file, n);
    dst[n - 1] = '\0';

    return dst;
}

#ifdef __KLIBC__

/* replacement of dlopen(). This limits a length of a base name up to 8
   characters. */
void *os2_dlopen(const char *file, int mode)
{
    char dll_file[strlen(file) + 1];

    return (dlopen)(os2_fixdllname(dll_file, file, sizeof(dll_file)), mode);
}

/* replacement of dlsym(). This prepends '_' to name. */
void *os2_dlsym(void *handle, const char *name)
{
    char sym[strlen(name) + 1 + 1]; /* 1 for '_', 1 for NUL */

    sym[0] = '_';
    strcpy(sym + 1, name);

    return (dlsym)(handle, sym);
}

#endif /* __KLIBC__ */

#endif /* __EMX__ */

#ifdef __MINGW32__

extern void *xmalloc (size_t);

int
setenv (const char *name, const char *value, int rewrite)
{
  char *entry;

  if (*value == '=')
    ++value;

  if (getenv (name) && !rewrite)
    return 0;

  entry = xmalloc (strlen (name) + 1 + strlen (value) + 1);
  strcat (strcat (strcpy (entry, name), "="), value);
  if (putenv (entry) != 0)
    {
      free (entry);
      return -1;
    }
  return 0;
}

int
unsetenv (const char *name)
{
  if (!name || !*name || strchr (name, '=') != NULL)
    return -1;

  return setenv (name, "", 1);
}

/* MinGW 3.21 and later defines usleep as an inline function in
   unistd.h, which conflicts with the version below.  */
#if __MINGW32_MAJOR_VERSION + (__MINGW32_MINOR_VERSION > 20) < 4
int
usleep(unsigned int usec)
{
  double msecf = usec / 1000.0;

  Sleep ((DWORD)msecf);

  return usec - msecf * 1000 < 0 ? 0 : (int)(usec - msecf * 1000);
}
#endif

/* The implementation of wctob in the MS runtime is problematic
   because it doesn't allow to distinguish between WEOF and 0xff, due
   to integer sign extension.  It also causes failures in dfa.c when
   characters with the 8th bit set are involved.  This replacement
   version fixes that.  */

#include <wchar.h>

int
wctob (wint_t wc)
{
  char buf[64];

  if (!(MB_CUR_MAX <= sizeof (buf)))
    abort ();
  /* Handle the case where WEOF is a value that does not fit in a wchar_t.  */
  if (wc == (wchar_t)wc)
    if (wctomb (buf, (wchar_t)wc) == 1)
      return (unsigned char) buf[0];
  return EOF;
}


#undef setlocale
#include <locale.h>

/* On Posix systems, 'setlocale' looks at LC_* variables in the
   environment, and Gawk users might expect that on Windows as well.
   The replacement implementation below does that, and also fixes a
   few additional quirks with locales on Windows.  */
static const char *
lc_var (int category)
{
  static const char *loc_name[LC_MAX - LC_MIN + 1] = {
    "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MONETARY", "LC_NUMERIC", "LC_TIME"
  };

  /* This function assumes LC_* categories are small numbers between 0
     and 5, as shown above, so if that changes at some point, complain
     vociferously.  */
  if (LC_ALL != 0 || LC_CTYPE != 2 || LC_TIME != 5)
    abort ();
  /* Ensured by the caller, so should never happen.  */
  if (category < LC_MIN || category > LC_MAX)
    return "????";
  return loc_name[category];
}

char *
w32_setlocale (int category, const char *value)
{
  const char *new_locale = value;

  if (LC_MIN <= category && category <= LC_MAX
      && value && *value == '\0')
    {
      const char *lc_val = getenv ("LC_ALL");

      if (!lc_val)
	lc_val = getenv (lc_var (category));
      if (!lc_val)
	lc_val = getenv ("LANG");
      if (lc_val)
	new_locale = lc_val;
    }

  /* If VALUE includes a codeset, i.e. a Windows codepage number, we
     must also set the LC_CTYPE locale to the same value, because
     LC_CTYPE is the only category which is documented to be able to
     change the codepage.  */
  if (category != LC_ALL && category != LC_CTYPE)
    {
      const char *p = strchr (new_locale, '.');

      if (p && isdigit (p[1]))
	setlocale (LC_CTYPE, new_locale);
    }
  return setlocale (category, new_locale);
}

/* Replacement for the missing nl_langinfo.  Only CODESET is currently
   supported.  */
#include <langinfo.h>

char *
nl_langinfo (int item)
{
  switch (item)
    {
      case CODESET:
	{
	  /* Shamelessly stolen from Gnulib's nl_langinfo.c.  */
	  static char buf[2 + 10 + 1];
	  char const *locale = setlocale (LC_CTYPE, NULL);
	  char *codeset = buf;
	  size_t codesetlen;
	  codeset[0] = '\0';

	  if (locale && locale[0])
	    {
	      /* If the locale name contains an encoding after the
		 dot, return it.  */
	      char *dot = strchr (locale, '.');

	      if (dot)
		{
		  /* Look for the possible @... trailer and remove it,
		     if any.  */
		  char *codeset_start = dot + 1;
		  char const *modifier = strchr (codeset_start, '@');

		  if (! modifier)
		    codeset = codeset_start;
		  else
		    {
		      codesetlen = modifier - codeset_start;
		      if (codesetlen < sizeof buf)
			{
			  codeset = memcpy (buf, codeset_start, codesetlen);
			  codeset[codesetlen] = '\0';
			}
		    }
		}
	    }
	  /* If setlocale is successful, it returns the number of the
	     codepage, as a string.  Otherwise, fall back on Windows
	     API GetACP, which returns the locale's codepage as a
	     number (although this doesn't change according to what
	     the 'setlocale' call specified).  Either way, prepend
	     "CP" to make it a valid codeset name.  */
	  codesetlen = strlen (codeset);
	  if (0 < codesetlen && codesetlen < sizeof buf - 2)
	    memmove (buf + 2, codeset, codesetlen + 1);
	  else
	    sprintf (buf + 2, "%u", GetACP ());
	  codeset = memcpy (buf, "CP", 2);

	  return codeset;
	}
      default:
	return (char *) "";
    }
}

/*
 * On MS-Windows with MinGW, execvp causes the shell and the re-exec'ed
 * dgawk to compete for the keyboard input.
 *
 * This will need work if we ever need a real version of execvp.
 */
int w32_execvp(const char *file, char **argv)
{
	if (_spawnvp(_P_WAIT, file, (const char * const *)argv) != -1)
		exit(EXIT_SUCCESS);

	return -1;
}

#ifdef DYNAMIC

#include <winerror.h>
#include <dlfcn.h>

static DWORD last_err;

void *
dlopen (const char *file, int mode)
{
  char dllfn[MAX_PATH], *p;
  HANDLE dllhandle;

  if (mode != RTLD_LAZY)
    {
      errno = EINVAL;
      last_err = ERROR_INVALID_PARAMETER;
      return NULL;
    }

  /* MSDN says to be sure to use backslashes in the DLL file name.  */
  strcpy (dllfn, file);
  for (p = dllfn; *p; p++)
    if (*p == '/')
      *p = '\\';

  dllhandle = LoadLibrary (dllfn);
  if (!dllhandle)
    last_err = GetLastError ();

  return dllhandle;
}

char *
dlerror (void)
{
  static char errbuf[1024];
  DWORD ret;

  if (!last_err)
    return NULL;

  ret = FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM
		       | FORMAT_MESSAGE_IGNORE_INSERTS,
		       NULL, last_err, 0, errbuf, sizeof (errbuf), NULL);
  while (ret > 0 && (errbuf[ret - 1] == '\n' || errbuf[ret - 1] == '\r'))
    --ret;

  errbuf[ret] = '\0';
  if (!ret)
    sprintf (errbuf, "Error code %lu", last_err);

  last_err = 0;
  return errbuf;
}

int
dlclose (void *handle)
{
  if (!handle || handle == INVALID_HANDLE_VALUE)
    return -1;
  if (!FreeLibrary (handle))
    return -1;

  return 0;
}

void *
dlsym (void *handle, const char *name)
{
  FARPROC addr = NULL;

  if (!handle || handle == INVALID_HANDLE_VALUE)
    {
      last_err = ERROR_INVALID_PARAMETER;
      return NULL;
    }

  addr = GetProcAddress (handle, name);
  if (!addr)
    last_err = GetLastError ();

  return (void *)addr;
}
#endif	/* DYNAMIC */

#ifdef HAVE_SOCKETS
int
socket_to_fd(SOCKET s)
{
  return (s == INVALID_SOCKET
	  ? INVALID_HANDLE
	  : _open_osfhandle (s, O_BINARY | O_NOINHERIT));
}

int
w32_socket(int family, int type, int protocol)
{
  /* We need to use WSASocket rather than socket, since the latter
     creates overlapped sockets that cannot be used in file I/O
     APIs.  */
  SOCKET s = WSASocket (family, type, protocol, NULL, 0, 0);

  if (s == INVALID_SOCKET)
    {
      switch (WSAGetLastError ())
	{
	  case WSAEMFILE:
	    errno = EMFILE;
	    break;
	  case WSANOTINITIALISED:
	  case WSAENETDOWN:
	    errno = EACCES;
	    break;
	  case WSAENOBUFS:
	    errno = ENOMEM;
	    break;
	  case WSAEFAULT:
	    errno = EFAULT;
	    break;
	  default:
	    errno = EINVAL;
	    break;
	}
    }

  return socket_to_fd (s);
}

int
w32_setsockopt (int fd, int level, int optname, const char *optval, int optlen)
{
  SOCKET s = FD_TO_SOCKET (fd);

  return setsockopt (s, level, optname, optval, optlen);
}

int
w32_bind (int fd, const struct sockaddr *name, int namelen)
{
  SOCKET s = FD_TO_SOCKET (fd);

  return bind (s, name, namelen);
}

int
w32_connect (int fd, const struct sockaddr *name, int namelen)
{
  SOCKET s = FD_TO_SOCKET (fd);

  return connect (s, name, namelen);
}

int
w32_listen (int fd, int backlog)
{
  SOCKET s = FD_TO_SOCKET (fd);

  return listen (s, backlog);
}

int
w32_accept (int fd, struct sockaddr *addr, int *addrlen)
{
  SOCKET s = FD_TO_SOCKET (fd);

  return socket_to_fd (accept (s, addr, addrlen));
}

SOCKET
valid_socket (int fd)
{
  SOCKET s = FD_TO_SOCKET (fd);
  int ov, ol = 4;

  if (s == INVALID_SOCKET
      || (getsockopt (s, SOL_SOCKET, SO_TYPE, (char *)&ov, &ol) == SOCKET_ERROR
	  && WSAGetLastError() == WSAENOTSOCK))
    return (SOCKET)0;
  return s;
}

int
w32_closesocket (int fd)
{
  SOCKET s = valid_socket (fd);
  int res1, res2 = 0;

  if (!s && fd == FAKE_FD_VALUE)
    return 0;

  res1 = close (fd);
  if (s)
    res2 = closesocket (s);

  if (res1 == -1 || res2 == SOCKET_ERROR)
    return -1;
  return 0;
}

int
w32_recvfrom (int fd, char *buf, int len, int flags,
	      struct sockaddr *from, int *fromlen)
{
  SOCKET s = FD_TO_SOCKET (fd);

  return recvfrom (s, buf, len, flags, from, fromlen);
}

int
w32_shutdown (int fd, int how)
{
  SOCKET s = FD_TO_SOCKET (fd);

  return shutdown (s, how);
}

#endif	/* HAVE_SOCKETS */

/* Translate abnormal exit status of Windows programs into the signal
   that terminated the program.  This is required to support scm_kill
   and WTERMSIG.  */

#include <signal.h>

struct signal_and_status {
  int sig;
  unsigned status;
};

static const struct signal_and_status sigtbl[] = {
  {SIGSEGV, 0xC0000005},	/* access to invalid address */
  {SIGSEGV, 0xC0000008},	/* invalid handle */
  {SIGILL,  0xC000001D},	/* illegal instruction */
  {SIGILL,  0xC0000025},	/* non-continuable instruction */
  {SIGSEGV, 0xC000008C},	/* array bounds exceeded */
  {SIGFPE,  0xC000008D},	/* float denormal */
  {SIGFPE,  0xC000008E},	/* float divide by zero */
  {SIGFPE,  0xC000008F},	/* float inexact */
  {SIGFPE,  0xC0000090},	/* float invalid operation */
  {SIGFPE,  0xC0000091},	/* float overflow */
  {SIGFPE,  0xC0000092},	/* float stack check */
  {SIGFPE,  0xC0000093},	/* float underflow */
  {SIGFPE,  0xC0000094},	/* integer divide by zero */
  {SIGFPE,  0xC0000095},	/* integer overflow */
  {SIGILL,  0xC0000096},	/* privileged instruction */
  {SIGSEGV, 0xC00000FD},	/* stack overflow */
  {SIGTERM, 0xC000013A},	/* Ctrl-C exit */
  {SIGINT,  0xC000013A}
};

int
w32_status_to_termsig (unsigned status)
{
  int i;

  for (i = 0; i < sizeof (sigtbl) / sizeof (sigtbl[0]); i++)
    if (status == sigtbl[i].status)
      return sigtbl[i].sig;

  return SIGTERM;
}

void
w32_maybe_set_errno (void)
{
  DWORD w32err = GetLastError ();

  switch (w32err)
    {
      /* When stdout is redirected to a pipe, and the program that
	 reads the pipe (e.g., a pager) exits, Windows doesn't set
	 errno to a useful value.  Help it DTRT.  */
      case ERROR_BAD_PIPE:
      case ERROR_PIPE_BUSY:
      case ERROR_NO_DATA:
      case ERROR_PIPE_NOT_CONNECTED:
	errno = EPIPE;
	break;
      default:
	errno = EINVAL;
	break;
    }
}

#endif	/* __MINGW32__ */

#if defined(__DJGPP__) || defined(__MINGW32__) || defined(__EMX__)

void
init_sockets(void)
{
#if defined(HAVE_SOCKETS) && !defined(__EMX__)
  WSADATA  winsockData;
  int errcode;

  if ((errcode = WSAStartup (0x101, &winsockData)) != 0
      || winsockData.wVersion != 0x101)
    fatal(_("cannot start Winsock (%d)"), errcode);
#endif
}

#endif	/* __DJGPP__ || __MINGW32__ */

#ifdef __DJGPP__

/* Replacement for the missing nl_langinfo.  DJGPP provides only C locale.  */
#include <langinfo.h>

char *
nl_langinfo (int item)
{
  switch (item)
    {
      case CODESET:
      default:
        return (char *) "";
    }
}


# if __DJGPP__ == 2 && __DJGPP_MINOR__ < 4 
int
unsetenv (const char *name)
{
  if (!name || !*name || strchr (name, '=') != NULL)
    return -1;

  return putenv (name);
}
#endif

/* This is needed to defeat too-clever GCC warnings in dfa.c about
   comparison being always false due to limited range of data type.  */
wint_t
btowc (int c)
{
  return c;
}

wint_t
putwc(wchar_t wc, FILE *stream)
{
  return MB_CUR_MAX == 1 && wc > 0 && wc <= UCHAR_MAX
         && putc((unsigned char)wc, stream) != EOF ? (wint_t)wc : WEOF;
}

#endif /* __DJGPP__ */
